package com.example.router.compiler.processor;

import com.example.router.annotation.Router;
import com.example.router.annotation.model.RouterMeta;
import com.example.router.compiler.utils.Constant;
import com.example.router.compiler.utils.EmptyUtils;
import com.example.router.compiler.utils.Log;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.io.IOException;
import java.util.*;

import static javax.lang.model.element.Modifier.PUBLIC;

/**
 * 路由注解处理器
 */
//作用是声明注解处理器
@AutoService(Processor.class)
//声明生成代码是基于java1.7
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//声明注解处理器支持的注解
@SupportedAnnotationTypes({Constant.ANY_TYPE_ROUTER})
public class RouterProcessor extends AbstractProcessor {

    /**
     * AutoService是谷歌开发的一个库，@AutoService 注解的作用是用来生成build\classes\java\main\META-INF\services\java.lang.Process文件的。
     * 也就是我们在使用注解处理器的时候，需要手动添加META-INF\services\java.lang.Process,而@AutoService
     * 就是帮我们自动生成。
     */
    private Filer filer;   // 文件生成器
    private Log log;    // 日志打印
    private Types typeUtils; // 类信息工具类

    private String moduleName;

    private Elements elementsUtils;  //结点工具

    private Map<String, String> rootMap = new TreeMap<>();  // key:组名 value:类名
    private Map<String, List<RouterMeta>> routerGroupMap = new HashMap<>(); // 分组  key：组名  value：对应路由信息

    /**
     * 初始化方法
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        log = Log.newLog(processingEnvironment.getMessager());// 初始化日志输出工具
        filer = processingEnvironment.getFiler();
        typeUtils = processingEnvironment.getTypeUtils();
        elementsUtils = processingEnvironment.getElementUtils();

        Map<String, String> options = processingEnvironment.getOptions();
        log.i("options内容：" + options.toString());
        if (!EmptyUtils.isEmpty(options)) {
            moduleName = options.get(Constant.ARGUMENTS_NAME);
        }
        if (EmptyUtils.isEmpty(moduleName)) {
            throw new RuntimeException("Not set Processor Parmaters.");
        }
    }

    /**
     * 此函数相当于Main函数，用于正式处理注解
     *
     * @param set              使用了支持处理的注解的节点集合
     * @param roundEnvironment 表示当前或者之前的运行环境，可以通过该对象查找找到的注解
     * @return true 表示后续处理器不会再处理（即已处理）
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        if (!EmptyUtils.isEmpty(set)) {
            // 获取被Router注解的元素集合
            Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Router.class);

            if (!EmptyUtils.isEmpty(elementsAnnotatedWith)) {
                // 处理Router注解
                try {
                    log.i("Router Class : " + elementsAnnotatedWith.size());
                    parseRoutes(elementsAnnotatedWith);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
        return false;
    }

    private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        TypeElement ability = elementsUtils.getTypeElement(Constant.ABILITY);
        TypeMirror type_ability = ability.asType();
        log.i("Route Class:ability ===" + type_ability);
        TypeElement slice = elementsUtils.getTypeElement(Constant.ABILITY_SLICE);
        TypeMirror type_slice = slice.asType();
        log.i("Route Class:slice ===" + type_slice);
        TypeElement iService = elementsUtils.getTypeElement(Constant.ISERVICE);
        TypeMirror type_IService = iService.asType();
        log.i("Route Class:iservice ===" + type_IService);

        /**
         * groupMap(组名:路由信息)集合
         */
        //声明 Route 注解的节点 (需要处理的节点 Activity/IService)
        for (Element element : routeElements) {
            //路由信息
            RouterMeta routeMeta;
            // 使用Route注解的类信息 (如activity)
            TypeMirror tm = element.asType();
            log.i("Route Class: " + tm.toString());
            Router route = element.getAnnotation(Router.class);
            //是否是 Activity 使用了Route注解
            /**
             * 判断Route注解使用在什么类上面
             */
            if (typeUtils.isSubtype(tm, type_ability)) {
                routeMeta = new RouterMeta(RouterMeta.Type.ABILITY, route, element);
            } else if (typeUtils.isSubtype(tm, type_slice)) {
                routeMeta = new RouterMeta(RouterMeta.Type.ABILITY_SLICE, route, element);
            } else if (typeUtils.isSubtype(tm, type_IService)) {
                routeMeta = new RouterMeta(RouterMeta.Type.ISERVICE, route, element);
            } else {
                throw new RuntimeException("[Just Support Activity/IService Route] :" + element);
            }
            //分组信息记录  groupMap <Group分组,RouteMeta路由信息> 集合
            //检查是否配置 group 如果没有配置，则从path截取出组名
            categories(routeMeta);
        }

        //生成类需要实现的接口
        TypeElement iRouteGroup = elementsUtils.getTypeElement(Constant.I_ROUTER_GROUP);
        TypeElement iRouteRoot = elementsUtils.getTypeElement(Constant.I_ROUTE_ROOT);

        log.i("iRouteRoot = " + iRouteRoot);

        /**
         *  生成Group类 作用:记录 <地址,RouteMeta路由信息(Class文件等信息)>
         */
        generatedGroup(iRouteGroup);
        /**
         * 生成Root类 作用:记录 <分组，对应的Group类>
         */
        generatedRoot(iRouteRoot, iRouteGroup);
    }

    // 记录路由信息 - 分组
    private void categories(RouterMeta routeMeta) {
        if (routerVerify(routeMeta)) {
            log.i("Group Info, Group Name = " + routeMeta.getGroup() + ", Path = " + routeMeta.getPath());

            List<RouterMeta> routeMetas = routerGroupMap.get(routeMeta.getGroup());
            //如果未记录分组则创建
            if (EmptyUtils.isEmpty(routeMetas)) {
                List<RouterMeta> routeMetaSet = new ArrayList<>();
                routeMetaSet.add(routeMeta);
                routerGroupMap.put(routeMeta.getGroup(), routeMetaSet);
            } else {
                routeMetas.add(routeMeta);
            }
        } else {
            log.i("Group Info Error: " + routeMeta.getPath());
        }
    }

    private void generatedGroup(TypeElement iRouteGroup) {
        //创建参数类型  Map<MapString, RouteMeta>
        ParameterizedTypeName atlas = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouterMeta.class));

        //创建参数名字  Map<MapString, RouteMeta> atlas
        ParameterSpec spec = ParameterSpec.builder(atlas, "atlas").build();

        //遍历分组， 每一个分组创建一个   ...$$$$Group$$ 类
        for (Map.Entry<String, List<RouterMeta>> stringListEntry : routerGroupMap.entrySet()) {

            //方法   public void loadInto(Map<MapString, RouteMeta> atlas) {}
            MethodSpec.Builder builder = MethodSpec.methodBuilder(Constant.METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .returns(TypeName.VOID)
                    .addParameter(spec);

            String groupName = stringListEntry.getKey();
            List<RouterMeta> groupData = stringListEntry.getValue();

            for (RouterMeta routeMeta : groupData) {
                /**
                 * atlas.put("/main/test", RouteMeta.build(RouteMeta.Type.ACTIVITY,SecondActivity.class, "/main/test", "main"));
                 */
                builder.addStatement("atlas.put($S,$T.build($T.$L,$T.class,$S, $S))",
                        routeMeta.getPath(),
                        ClassName.get(RouterMeta.class),
                        ClassName.get(RouterMeta.Type.class),
                        routeMeta.getType(),
                        ClassName.get((TypeElement) routeMeta.getElement()),
                        routeMeta.getPath().toLowerCase(),
                        routeMeta.getGroup().toLowerCase());
            }

            //ARouter$$Group$$main
            String groupClassName = Constant.NAME_OF_GROUP + groupName;
            try {
                JavaFile.builder(Constant.PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(groupClassName)
                        .addSuperinterface(ClassName.get(iRouteGroup))
                        .addModifiers(PUBLIC)
                        .addMethod(builder.build()).build()
                ).build().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            log.i("Generated RouteGroup: " + Constant.PACKAGE_OF_GENERATE_FILE + "." + groupClassName);
            rootMap.put(groupName, groupClassName);
        }
    }

    private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) throws IOException {
        ParameterizedTypeName routes = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
                )
        );

        //参数 Map<String,Class<? extends IRouteGroup>> routes> routes
        ParameterSpec rootParamSpec = ParameterSpec.builder(routes, "routes")
                .build();
        //函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
        MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder
                (Constant.METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(rootParamSpec);

        //函数体
        for (Map.Entry<String, String> entry : rootMap.entrySet()) {
            loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry
                    .getKey(), ClassName.get(Constant.PACKAGE_OF_GENERATE_FILE, entry.getValue()));
        }
        //生成 $Root$类
        String rootClassName = Constant.NAME_OF_ROOT + moduleName;
        JavaFile.builder(Constant.PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(rootClassName)
                        .addSuperinterface(ClassName.get(iRouteRoot))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfRootBuilder.build())
                        .build()
        ).build().writeTo(filer);

        log.i("Generated RouteRoot: " + Constant.PACKAGE_OF_GENERATE_FILE + "." + rootClassName);
    }

    private boolean routerVerify(RouterMeta routerMeta) {
        String path = routerMeta.getPath();
        String group = routerMeta.getGroup();
        log.i("Path = " + path + ", Group = " + group);
        if (EmptyUtils.isEmpty(path) || !path.startsWith("/")) {
            return false;
        }
        if (EmptyUtils.isEmpty(group)) {
            String substring = path.substring(1, path.indexOf("/", 1));
            if (EmptyUtils.isEmpty(substring)) {
                return false;
            }
            routerMeta.setGroup(substring);
            return true;
        }
        return true;
    }
//
//    @Override
//    public SourceVersion getSupportedSourceVersion() {
//        return SourceVersion.RELEASE_7;
//    }
//
//    @Override
//    public Set<String> getSupportedAnnotationTypes() {
//        return super.getSupportedAnnotationTypes();
//    }
}
